package com.fll.server;


import com.fll.protocol.Constants;
import com.fll.server.config.ProxyConfig;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author fanlei
 * @version 1.0
 * @date 2019年01月25日
 */
public class ProxyChannelManager {
    private static Logger logger = LoggerFactory.getLogger(ProxyChannelManager.class);

    private static final AttributeKey<Map<String, Channel>> USER_CHANNELS = AttributeKey.newInstance("user_channels");

    private static final AttributeKey<String> REQUEST_LAN_INFO = AttributeKey.newInstance("request_lan_info");

    private static final AttributeKey<List<Integer>> CHANNEL_PORT = AttributeKey.newInstance("channel_port");

    private static final AttributeKey<String> CHANNEL_CLIENT_KEY = AttributeKey.newInstance("channel_client_key");

    private static Map<Integer, Channel> portCmdChannelMapping = new ConcurrentHashMap();

    private static Map<String, Channel> cmdChannels = new ConcurrentHashMap();

    public ProxyChannelManager() {
    }

    public static void addCmdChannel(List<Integer> ports, String clientKey, Channel channel) {
        if (ports == null) {
            throw new IllegalArgumentException("port can not be null");
        } else {
            Map var3 = portCmdChannelMapping;
            synchronized(portCmdChannelMapping) {
                Iterator it = ports.iterator();

                while(true) {
                    if (!it.hasNext()) {
                        break;
                    }

                    int port = (Integer)it.next();
                    portCmdChannelMapping.put(port, channel);
                }
            }

            channel.attr(CHANNEL_PORT).set(ports);
            channel.attr(CHANNEL_CLIENT_KEY).set(clientKey);
            channel.attr(USER_CHANNELS).set(new ConcurrentHashMap());
            cmdChannels.put(clientKey, channel);
        }
    }

    public static void removeCmdChannel(Channel channel) {
        logger.warn("channel closed, clear user channels, {}", channel);
        
        if (channel.attr(CHANNEL_PORT).get() != null) {
            String clientKey = (String)channel.attr(CHANNEL_CLIENT_KEY).get();
            Channel channel0 = (Channel)cmdChannels.remove(clientKey);
            if (channel != channel0) {
                cmdChannels.put(clientKey, channel);
            }

            List<Integer> ports = (List)channel.attr(CHANNEL_PORT).get();
            Iterator it = ports.iterator();

            Channel userChannel;
            while(it.hasNext()) {
                int port = (Integer)it.next();
                userChannel = (Channel)portCmdChannelMapping.remove(port);
                if (userChannel != null && userChannel != channel) {
                    portCmdChannelMapping.put(port, userChannel);
                }
            }

            if (channel.isActive()) {
                logger.info("disconnect proxy channel {}", channel);
                channel.close();
            }

            Map<String, Channel> userChannels = getUserChannels(channel);
            Iterator ite = userChannels.keySet().iterator();

            while(ite.hasNext()) {
                userChannel = (Channel)userChannels.get(ite.next());
                if (userChannel.isActive()) {
                    userChannel.close();
                    logger.info("disconnect user channel {}", userChannel);
                }
            }

        }
    }

    public static Channel getCmdChannel(Integer port) {
        return (Channel)portCmdChannelMapping.get(port);
    }

    public static Channel getCmdChannel(String clientKey) {
        return (Channel)cmdChannels.get(clientKey);
    }

    public static void addUserChannelToCmdChannel(Channel cmdChannel, String userId, Channel userChannel) {
        InetSocketAddress sa = (InetSocketAddress)userChannel.localAddress();
        String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
        userChannel.attr(Constants.USER_ID).set(userId);
        userChannel.attr(REQUEST_LAN_INFO).set(lanInfo);
        ((Map)cmdChannel.attr(USER_CHANNELS).get()).put(userId, userChannel);
    }

    public static Channel removeUserChannelFromCmdChannel(Channel cmdChannel, String userId) {
        if (cmdChannel.attr(USER_CHANNELS).get() == null) {
            return null;
        } else {
            synchronized(cmdChannel) {
                return (Channel)((Map)cmdChannel.attr(USER_CHANNELS).get()).remove(userId);
            }
        }
    }

    public static Channel getUserChannel(Channel cmdChannel, String userId) {
        return (Channel)((Map)cmdChannel.attr(USER_CHANNELS).get()).get(userId);
    }

    public static String getUserChannelUserId(Channel userChannel) {
        return (String)userChannel.attr(Constants.USER_ID).get();
    }

    public static String getUserChannelRequestLanInfo(Channel userChannel) {
        return (String)userChannel.attr(REQUEST_LAN_INFO).get();
    }

    public static Map<String, Channel> getUserChannels(Channel cmdChannel) {
        return (Map)cmdChannel.attr(USER_CHANNELS).get();
    }

    static {
        ProxyConfig.getInstance().addConfigChangedListener(new ProxyConfig.ConfigChangedListener() {
            public synchronized void onChanged() {
                Iterator ite = ProxyChannelManager.cmdChannels.entrySet().iterator();

                while(true) {
                    while(ite.hasNext()) {
                        Channel proxyChannel = (Channel)((Map.Entry)ite.next()).getValue();
                        String clientKey = (String)proxyChannel.attr(ProxyChannelManager.CHANNEL_CLIENT_KEY).get();
                        Set<String> clientKeySet = ProxyConfig.getInstance().getClientKeySet();
                        if (!clientKeySet.contains(clientKey)) {
                            ProxyChannelManager.removeCmdChannel(proxyChannel);
                        } else if (proxyChannel.isActive()) {
                            List<Integer> inetPorts = new ArrayList(ProxyConfig.getInstance().getClientInetPorts(clientKey));
                            Set<Integer> inetPortSet = new HashSet(inetPorts);
                            List<Integer> channelInetPorts = new ArrayList((Collection)proxyChannel.attr(ProxyChannelManager.CHANNEL_PORT).get());
                            synchronized(ProxyChannelManager.portCmdChannelMapping) {
                                Iterator it = channelInetPorts.iterator();

                                int chanelInetPort;
                                while(it.hasNext()) {
                                    chanelInetPort = (Integer)it.next();
                                    Channel channel = (Channel)ProxyChannelManager.portCmdChannelMapping.get(chanelInetPort);
                                    if (channel != null && proxyChannel == channel) {
                                        if (!inetPortSet.contains(chanelInetPort)) {
                                            ProxyChannelManager.portCmdChannelMapping.remove(chanelInetPort);
                                            ((List)proxyChannel.attr(ProxyChannelManager.CHANNEL_PORT).get()).remove(new Integer(chanelInetPort));
                                        } else {
                                            inetPorts.remove(new Integer(chanelInetPort));
                                        }
                                    }
                                }

                                it = inetPorts.iterator();

                                while(it.hasNext()) {
                                    chanelInetPort = (Integer)it.next();
                                    ProxyChannelManager.portCmdChannelMapping.put(chanelInetPort, proxyChannel);
                                    ((List)proxyChannel.attr(ProxyChannelManager.CHANNEL_PORT).get()).add(chanelInetPort);
                                }

                                this.checkAndClearUserChannels(proxyChannel);
                            }
                        }
                    }

                    ite = ProxyChannelManager.cmdChannels.entrySet().iterator();

                    while(ite.hasNext()) {
                        Map.Entry<String, Channel> entry = (Map.Entry)ite.next();
                        Channel proxyChannelx = (Channel)entry.getValue();
                        ProxyChannelManager.logger.info("proxyChannel config, {}, {}, {} ,{}", new Object[]{entry.getKey(), proxyChannelx, ProxyChannelManager.getUserChannels(proxyChannelx).size(), proxyChannelx.attr(ProxyChannelManager.CHANNEL_PORT).get()});
                    }

                    return;
                }
            }

            private void checkAndClearUserChannels(Channel proxyChannel) {
                Map<String, Channel> userChannels = ProxyChannelManager.getUserChannels(proxyChannel);
                Iterator userChannelIte = userChannels.entrySet().iterator();

                while(true) {
                    Map.Entry entry;
                    Channel userChannel;
                    String requestLanInfo;
                    String lanInfo;
                    do {
                        if (!userChannelIte.hasNext()) {
                            return;
                        }

                        entry = (Map.Entry)userChannelIte.next();
                        userChannel = (Channel)entry.getValue();
                        requestLanInfo = ProxyChannelManager.getUserChannelRequestLanInfo(userChannel);
                        InetSocketAddress sa = (InetSocketAddress)userChannel.localAddress();
                        lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
                    } while(lanInfo != null && lanInfo.equals(requestLanInfo));

                    userChannel.close();
                    userChannels.remove(entry.getKey());
                }
            }
        });
    }
}
